Channel可以想像成是一種資料結構,可以push data進去也可以pull data出來。
因為Channel會等待另一端完成Push/Pull的動作才會繼續往下處理,而且特性使其可以在Goroutines間同步處理的資料,而不用使用lock, unlock等方式。
這裡示範建立一個int型別的channel
ch := make(chan int)
將data d 送入channel ch之中
ch <- d
將data從channel ch拿出,並賦予給變數d
d := <- ch
Channel是用來讓Goroutine溝通時使用的一種資料結構,並且由於其阻塞的特性,它也能夠當成一種等待goroutine的方法。
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan string)
go calculator(ch)
time.Sleep(3 * time.Second)
fmt.Println(<-ch)
time.Sleep(time.Second)
fmt.Println("main goroutine finished")
}
func calculator(ch chan string) {
fmt.Println("Start to calculate goroutines")
time.Sleep(time.Second)
fmt.Println("Stop to calculate goroutines")
ch <- "FINISH"
fmt.Println("Finish calculator")
}
運行後可得以下結果
Start to calculate goroutines
Stop to calculate goroutines
FINISH
Finish calculator
Main goroutine finished
那我們這邊的三秒延遲目的是為了讓main thread慢於goroutines,一秒延遲則是模擬goroutines的作業時間! 所以他會依序的去進行goroutines作業→打印channel的內容→完成goroutines function→完成main thread。
通道分為兩種,有buffer與無buffer的,也就是有儲存空間限制的channel與無限制的channel。
此外,通道也能分為單向與雙向兩種,就是能傳資料的caller
vs只有一方能傳資料的callee
。
Variable := make(chan Type)
chan
被製造出來之後,需要傳入 要被你併發出去的func
,它靠這種方式傳資料。
chan
是有方向性的,要看箭頭←
的方向。
chan <- A `把 A這個東西 塞進chan`
B<- chan `從chan 挖東西出來 到B`
由下兩範例來說明接收的方向性:
package main
import (
"fmt"
)
func main() {
ch := make(chan int)
go func1(ch)
ch <- 100
}
func func1(ch chan int) {
i := <-ch
fmt.Println(i)
}
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int)
go func2(ch)
got := <-ch
fmt.Println(got)
}
func func2(ch chan int) {
time.Sleep(time.Second * 2)
ch <- 999
}
運行後可得以下結果
999
由上面例子我們可以得知,Unbuffered Channel有一個特性:
也因此如果Pusher的執行一次時間較Puller短,會造成Pusher被迫等待Puller拉取才能進行下一次的push,而這樣的等待是相當浪費時間的。
為了解決此問題,因此誕生了另一種Channel,Buffered Channel。
Variable := make(chan Type, Number)
有限制儲存空間的通道,若限制放兩個,就只能有兩筆數據,倘若塞入第三筆的話則會造成死結DeadLock
package main
func main() {
ch := make(chan int, 2)
go func3(ch)
ch <- 100
ch <- 99
ch <- 98 // 發生deadlock
}
func func3(ch chan int) {
}
運行後可得以下結果
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:
main.main()
/tmp/sandbox241095799/prog.go:9 +0xa7
以上例來說,通常chan塞不下第三筆數據時,只會發生Block(阻塞滯留)
,而當Block
永遠無法解開的情況發生,則是 Deadlock(死結)
。上面會發生死結是因為 不論等多久,都不會從Block
的狀態中脫離。
只要通道(Chan)
塞不下,或者沒東西可挖,都會發生Block
阻塞。
以下是通道Channel 阻塞Block
的例子
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int, 2)
go func1(ch)
for i := 0; i < 10; i++ {
ch <- i
fmt.Println("main sent", i)
}
time.Sleep(time.Second)
}
func func1(ch chan int) {
for {
i := <-ch
fmt.Println("func1 got", i)
time.Sleep(time.Millisecond * 100)
}
}
運行後可得以下結果
main sent 0
main sent 1
main sent 2
func1 got 0
func1 got 1
main sent 3
func1 got 2
main sent 4
func1 got 3
main sent 5
func1 got 4
main sent 6
func1 got 5
main sent 7
func1 got 6
main sent 8
func1 got 7
main sent 9
func1 got 8
func1 got 9
主程式不間斷地連續塞十次數字
送完休息1秒;而func1
每0.1秒吃下來一個數值。
雖然慢,但程式不會打死結
,只是會造成執行效率低下而已。
如果把Buffer Size: 2換成5
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int, 2)
go func1(ch)
for i := 0; i < 10; i++ {
ch <- i
fmt.Println("main sent", i)
}
time.Sleep(time.Second)
}
func func1(ch chan int) {
for {
i := <-ch
fmt.Println("func1 got", i)
time.Sleep(time.Millisecond * 100)
}
}
運行後可得以下結果
main sent 0
main sent 1
main sent 2
main sent 3
main sent 4
main sent 5
func1 got 0
func1 got 1
main sent 6
func1 got 2
main sent 7
func1 got 3
main sent 8
func1 got 4
main sent 9
func1 got 5
func1 got 6
func1 got 7
func1 got 8
func1 got 9
同時間通道裡最多會有五個數字。
塞與取的先後順序,透過log.SetFlags(5)
來看會比較清楚。
The buffer size is the number of elements that can be sent to the channel without the send blocking.
Buffer 是拿來緩衝用的,Unbuffered Channel則是0緩衝
,就是沒有緩衝啦!Unbuffered 是需要有同時有一頭寫入、另一頭讀出,才能動的。
當然是 沒有!
因為倘若需要給個100000byte大小的Channel必須先挪出100000byte的記憶體空間出來,如此一來多創幾個Unlimited Chaneel就會造成Out Of Memory了!
這章節我們講述了Channel的用法,Unbuffered與Buffered的差別,彼此所遇到的問題與困難,以及它們各自搭配goroutines的使用範例,